/* -+- file  ./kf2/Net/Account.cs -+- */
/*
 * kf2 jabber client
 * ~~~~~~~~~~~~~~~~~
 *
 * Copyright (C) 2006-2007 Przemysław Sitek
 * 				<przemyslaw.sitek at wp.pl> 
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

using System;
using System.Text;
using System.Xml;
using Habazie.kf2.Core; 
using Habazie.kf2.Roster; 
using System.Security.Cryptography.X509Certificates;

namespace Habazie.kf2.Net
{
	///<summary>Account state</summary>
	public enum AccountState {
		///<summary>Disconnected</summary>
		Disconnected,
		
		///<summary>Connecting</summary>
		Connecting,
		
		///<summary>Connected</summary>
		Connected
	}
	
	public delegate void PasswordEventHandler (object o, PasswordEventArgs args);
	
	///<summary>A connection to Jabber service</summary>
	public class Account : ExtendedClient, GUI.IRosterItem
	{
		#region Private fields
		///<summary>Connection name</summary>
		private string m_Name;
		
		private bool m_SavePassword = false;
		
		///<summary>Roster associated with this account</summary>
		private Roster.Roster m_Roster;
		
		///<summary>Account State</summary>
		private AccountState m_State = AccountState.Disconnected;
		
		/* last status */
		private jabber.protocol.client.Presence m_presence = null;


		private jabber.protocol.client.PresenceType m_StatusType = jabber.protocol.client.PresenceType.unavailable;
		private string	m_StatusShow = null;
		private string  m_StatusDesc = null;
		
		private bool	m_rosterReceived = false; 
		
		///<summary>Item representing yourself</summary>
		private Item	m_Self;
		
		#endregion
		#region Events
		
		public event System.EventHandler Changed = null;
		public event PasswordEventHandler PasswordNeeded = null;
		
		#endregion
		
		static Account ()
		{
			// this enables StartTLS... dunno why...
			bedrock.net.AsyncSocket.UntrustedRootOK = true;
		}
		
		public Account() : base ()
		{
			AutoLogin = true;
		//	AutoStartTLS = false;
			AutoPresence = false;
			AutoRoster = false;
			Priority = 7;
			
			Resource = Util.APPNAME;
			OnConnect += delegate {
				Console.WriteLine ("Connected;)");
			};
			OnAuthenticate += Authenticated;
			OnStreamInit += StreamInit;
			OnPresence += PresenceReceived;
			OnMessage += MessageReceived;
			OnIQ += IQReceived;
			OnError += ErrorReceived;
			OnDisconnect += Disconnected;
			
			OnReadText += TextRead;
			
			m_Roster = new Roster.Roster (this);
			
			m_Self = new Item ();

			Changed += OnChanged;
//			this.
		}
		
		
		///<summary>Load account data from a xml description</summary>
		public Account (XmlNode root) : this ()
		{
			m_Name = ((XmlElement) root).GetAttribute ("name"); 
			foreach (XmlNode node in root.ChildNodes) {
				string val = node.InnerText;
			//	Habazie.kf2.Core.Util.Debug ("Core", String.Format ("<{0}>{1}</{0}>", node.Name, val));
				switch (node.Name) {
					case "auto-reconnect":
						AutoReconnect = (val == "True")?30:-1; break;
					case "network-host":
						NetworkHost = val; break;
					case "save-pass":
						SavePassword = val == "True";break;
					case "password":
						Password = val; break;
				//	case "pass":
				//		try {
				//			Password = Convert.FromBase64String (val).ToString ();
				//		} catch {}
				//		break;
					case "plaintext-auth":
						PlaintextAuth = val == "True";break;
					case "port":
						Port = Int32.Parse (val); break;
					case "priority":
						Priority = Int32.Parse (val); break;
					case "resource":
						Resource = val; break;
					case "server":
						Server = val; break;
					case "user":
						User = val; break;
					case "tls":
						AutoStartTLS = val == "True"; break;
					case "ssl":
						SSL = val == "True"; break;
				}
			}
			m_Self.JabberID = new jabber.JID (this.User, this.Server, null);
		}
		
		public void Store (XmlWriter xml)
		{
			xml.WriteStartElement ("account");
			xml.WriteAttributeString ("name", Name);
			xml.WriteElementString ("auto-reconnect", (AutoReconnect>-1)?"True":"False");
			xml.WriteElementString ("network-host", NetworkHost);
//			xml.WriteElementString ("password", Password);
			xml.WriteElementString ("save-pass", SavePassword.ToString ());
			if (SavePassword)
				xml.WriteElementString ("password", Password);
//				xml.WriteElementString ("pass", Convert.ToBase64String (Password.ToCharArray ()));
			xml.WriteElementString ("plaintext-auth", PlaintextAuth.ToString ());
			xml.WriteElementString ("port", Port.ToString ());
			xml.WriteElementString ("priority", Priority.ToString ());
			xml.WriteElementString ("resource", Resource);
			xml.WriteElementString ("server", Server);
			xml.WriteElementString ("user", User);
			xml.WriteElementString ("tls", AutoStartTLS.ToString ());
			xml.WriteElementString ("ssl", SSL.ToString ());
			xml.WriteEndElement ();
		}
		
		#region Properties
		
		public string Name {
			get { return m_Name; }
			set { m_Name = value; }
		}
		
		public bool SavePassword {
			get { return m_SavePassword; }
			set { m_SavePassword = value; }
		}
		
		public AccountState NetworkState {
			get { return m_State; }
		}
		
		public Roster.Roster GetRosterRef ()
		{
			return m_Roster;
		}
		
		public jabber.protocol.client.PresenceType StatusType {
			get { return m_StatusType; }
		}
		public string StatusShow {
			get { return m_StatusShow; }
		}
		public string StatusText {
			get { return m_StatusDesc; }
		}
		public Item SelfItem {
			get { return m_Self; }
		}
		public string Description {
			get {
				if (NetworkState == AccountState.Connecting)
					return "Connecting...";
//				else if (NetworkState == AccountState.Disconnected)
//					return "Disconnected";
				else if (StatusText == null)
					return Util.StatusToString (StatusType, StatusShow);
				else
					return Util.EscapeString (StatusText);
			}
		}
		
		#endregion
		#region Methods
		
		public void FireChanged ()
		{
			if (Changed != null)
				Changed (this,new EventArgs ());
		}


		public override void Connect()
		{
			Console.WriteLine ("Connect ()");
			m_State = AccountState.Connecting;
			
			if ((Password == null || Password == "") && PasswordNeeded != null) {
				PasswordEventArgs args = new PasswordEventArgs ();
				PasswordNeeded (this, args);
				if (args.Password != null && args.Password != "") {
					Password = args.Password;
				} else {
					m_State = AccountState.Disconnected;
					return;
				}
			}
			
			Util.Debug ("Net", "Calling base.Connect ()");
			base.Connect ();
			if (Changed != null)
				Changed (this,new EventArgs ());
		}
		
		public virtual void Presence (jabber.protocol.client.PresenceType t, string status, string show)
		{
			Console.WriteLine ("Sending presence");
//			m_StatusType = t;
//			m_StatusShow = show;
//			m_StatusDesc = status;
//			base.Presence (t, status, show, Priority);
			jabber.protocol.client.Presence p = new jabber.protocol.client.Presence (this.Document);
			p.Type = t;
			
			if (status != null)
				p.Status = status;
				
			if (show != null)
				p.Show = show;
				
			p.Priority = this.Priority.ToString ();
				
			/* Entity capabilities */
			XmlElement c = this.Document.CreateElement ("c", "http://jabber.org/protocol/caps");
			c.SetAttribute ("node", "http://code.google.com/p/talkative/caps");
			c.SetAttribute ("ver", Util.VERSION);
			p.AppendChild (c);
			if (IsAuthenticated) {
				/* We may send it */
				this.Write (p);
			
				/* Update icons on roster view */
				if (Changed != null)
					Changed (this,new EventArgs ());
			} else {
				/* Store it and send later... when we are connected */
				m_presence = p;
			}
		}
		
		
		/// <summary>
		/// 	Write Xml Element 	
		/// </summary>
		public void Write (jabber.protocol.Element el)
		{
		//	Util.Debug ("net", "Account.Write ()");
			Message msg = el as Message;
			if (msg != null && msg.Body != null)
				Util.Application.ArchivingService.Archive (this, msg, msg.To.Bare, false);
			
			base.Write (el);
		}
		
		
		///<summary>
		///	Set status for this account. This method connects or disconnects if necesary.
		///</summary>
		public virtual void SetStatus (jabber.protocol.client.PresenceType t, string status, string show)
		{
			/* Store status information */
			this.m_StatusType = t;
			this.m_StatusShow = show;
			this.m_StatusDesc = status;

			/* Check if connection is open */
			if (t == jabber.protocol.client.PresenceType.available
				&& ! this.IsAuthenticated /* FIXME: connecting is sufficient */) {
				Connect ();
			} else {
				/* Connection is open */
			//	Presence (t, status, show);
			//	Presence() can be used on closed connection
			}
			Presence (t, status, show);
			
			/* Close connection if necessary */
			if (t == jabber.protocol.client.PresenceType.unavailable) {
				/* Close connection */
				Close ();
			}
		}

		#endregion
		
		
		///<summary>Connection has just been estabilished</summary>
		protected virtual void Authenticated (object sender)
		{
			m_State = AccountState.Connected;
			if (m_presence != null)
				Write (m_presence);
			
			Gtk.Application.Invoke (delegate {
				if (Changed != null)
					Changed (this,new EventArgs ());
			});
			GetRoster ();
			Util.Debug ("Core", "Fetching my info...");
			m_Self.UpdateVCard ();
		}
		
		
		private void StreamInit (object  sender, jabber.protocol.ElementStream stream)
		{
			Console.WriteLine ("StreamInit");
			// TODO: make this work!
			stream.AddFactory (new Habazie.kf2.Net.Factory ());
			stream.AddFactory (new Habazie.kf2.Net.protocol.Factory ());
		}
		
		protected virtual void PresenceReceived (object sender, jabber.protocol.client.Presence presence)
		{
			try {
				Gtk.Application.Invoke (delegate {
					m_Roster.Update (presence);
				});
			} catch (Exception e) {
				Console.Error.WriteLine ("Presence: exception: {0}@{1}", e.Message, e.Source);
				Console.WriteLine ("{0}", e.StackTrace);
			}
		}
		
		protected virtual void MessageReceived (object sender, jabber.protocol.client.Message message)
		{
			Console.WriteLine ("t: {0} ({1})", message.GetType ().FullName, message is Habazie.kf2.Net.Message);
		}
		
		protected virtual void IQReceived (object sender, jabber.protocol.client.IQ iq)
		{
			try {
				if (iq.Query is jabber.protocol.iq.Roster) {
					Util.Debug ("net", "Got roster");
					if (! m_rosterReceived) {
						m_rosterReceived = true;
						Presence (m_StatusType, m_StatusShow, m_StatusDesc);
					}
//					lock (this) {
					Gtk.Application.Invoke (delegate {
						m_Roster.Update ((jabber.protocol.iq.Roster) iq.Query);
					});
				}
			} catch (Exception e) {
				Util.Debug ("account","Whoops! Exception: " + e.Message);
				Console.Error.Write ("<stack>{0}</stack>", e.StackTrace);
			}
		}
		
		protected virtual void ErrorReceived (object sender, Exception e)
		{
			m_State = AccountState.Disconnected;
			m_StatusType = jabber.protocol.client.PresenceType.unavailable;
			Gtk.Application.Invoke (delegate {
				if (Changed != null)
					Changed (this,new EventArgs ());
			});
			Console.Error.WriteLine ("+ Error: {0}", e.Message);
			Util.ReportException (e);
			Console.Error.WriteLine ("{0}", this);
		}
		
		protected virtual void OnChanged (object o, EventArgs args)
		{
			m_Self.JabberID = new jabber.JID (this.User, this.Server, null);
		}
		
		protected virtual void Disconnected (object o)
		{
			m_State = AccountState.Disconnected;
			m_StatusType = jabber.protocol.client.PresenceType.unavailable;
			m_rosterReceived = false;
			Gtk.Application.Invoke (delegate {
				m_Roster.Disconnected ();
				if (Changed != null)
					Changed (this,new EventArgs ());
			});
			Console.Error.WriteLine ("Disconnected.");
			Console.Error.WriteLine ("{0}", this);
		}
		
		protected virtual void GotRoster (object sender, System.Xml.XmlElement element)
		{
//			Type t = element.GetType ();
//			Console.WriteLine ("roster: got {0}", t.FullName);
//			Console.WriteLine ("roster: {0}", element);
		}
		
		protected virtual void TextRead (object sender, string txt)
		{
//			Console.WriteLine ("READ\n----\n{0}", txt);
		}
		
		
		/// <summary>
		/// 	Create a temporary item
		/// </summary>
		public Item CreateTemporaryItem (string jid)
		{
			Item i = new Item ();
			i.JabberID = new jabber.JID (jid);
			i.Account = this;
			return i;
		}
		
		public void DisplayAtIter (Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			Gdk.Pixbuf pix = Gdk.Pixbuf.LoadFromResource ("lock.png");
		//	if (pix == null)
		//		pix = 
			model.SetValue (iter, 0, this);
		//	model.SetValue (iter, 1, null);
			model.SetValue (iter, 2, String.Format ("{0}", Name));
			model.SetValue (iter, 3, pix);	
		}

		public override string ToString ()
		{
			StringBuilder b = new StringBuilder ();
			b.AppendFormat ("Account {0}\n", Name);
			b.AppendFormat (" * JID <{0}@{1}/{2}>\n", User, Server, Resource);
			b.AppendFormat (" * Connect to {0}\n", NetworkHost);
			b.AppendFormat (" * TLS {0} StreamID\n", SSLon, StreamID);
			b.AppendFormat (" * Authenticated {0}\n", IsAuthenticated);
			b.AppendFormat (" * State {0}\n", NetworkState);
//			b.AppendFormat (" * ");
			return b.ToString ();

		}
	}
	
	public class PasswordEventArgs : EventArgs
	{
		private string m_pass = null;
		
		public string Password {
			get { return m_pass; }
			set { m_pass = value; }
		}
	}
		
}
